Skip to content

feat: expose query operation timings#2491

Merged
StarpTech merged 4 commits intowundergraph:mainfrom
AlenaSviridenko:asviridenko-expose-operation-timings
Feb 11, 2026
Merged

feat: expose query operation timings#2491
StarpTech merged 4 commits intowundergraph:mainfrom
AlenaSviridenko:asviridenko-expose-operation-timings

Conversation

@AlenaSviridenko
Copy link
Copy Markdown
Contributor

@AlenaSviridenko AlenaSviridenko commented Feb 5, 2026

Corresponding docs PR wundergraph/cosmo-docs#248

Why this change is needed

Our app uses a custom Go module to publish detailed per-request telemetry to our internal Hydro system (Kafka-based). We need GraphQL operation performance metrics (parse, normalize, validate, plan timing) for every request to analyze query performance and debug issues.

While we already export OTEL metrics to Datadog, we need the raw per-request timing data in our telemetry pipeline. Currently, these timings are only accessible by passing the X-WG-Trace header, which has several problems:

  1. Can't control client behavior - We can't require all clients to pass a debugging header
  2. Security risk - The trace response exposes internal subgraph implementation details to clients
  3. Incomplete coverage - Only requests with the header get timing data, preventing comprehensive analysis
  4. Performance overhead - Trace responses are verbose and add unnecessary payload size

This PR exposes operation timings via the OperationContext interface (similar to the existing QueryPlanStats() method), allowing custom modules to access this data programmatically without modifying responses or requiring client headers.

This enables us to:

  • Capture timing metrics for 100% of requests
  • Keep internal metrics separate from client-facing responses
  • Maintain security by not exposing implementation details
  • Avoid response payload overhead

Summary by CodeRabbit

  • New Features

    • Added detailed operation timing metrics (parsing, validation, planning, normalization) for GraphQL requests.
    • Added a middleware module to capture and asynchronously deliver operation timing data for custom integrations without impacting request flow.
  • Tests

    • Added tests validating timing metrics for simple and complex queries to ensure non-negative and meaningful durations.

Checklist

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 5, 2026

Walkthrough

Adds an OperationTimings struct and Timings() methods to expose parsing, validation, planning, and normalization durations; introduces a router middleware module that captures those timings and sends them over a channel; and adds tests that run queries and assert timing fields are non-negative and at least one is non-zero.

Changes

Cohort / File(s) Summary
Core Timing Infrastructure
router/core/context.go
Added exported OperationTimings (ParsingTime, ValidationTime, PlanningTime, NormalizationTime) and a Timings() method on OperationContext/operationContext to return current timing values.
Custom Operation Timings Module
router-tests/modules/custom-operation-timings/module.go
New middleware OperationTimingsModule implementing core.RouterMiddlewareHandler; exposes ResultsChan chan core.OperationTimings, non-blockingly sends timings from ctx.Operation().Timings() in Middleware, and provides Module() metadata and factory.
Operation Timings Tests
router-tests/modules/operation_timings_test.go
New test TestCustomModuleOperationTimings with subtests that register the custom module, execute simple and complex GraphQL requests, read timings from the module channel, and assert timing values are non-negative and at least one is non-zero.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: expose query operation timings' directly and accurately summarizes the main change: exposing GraphQL operation timings through the OperationContext interface.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@router-tests/modules/custom-operation-timings/module.go`:
- Around line 16-23: The middleware OperationTimingsModule.Middleware currently
does a blocking send m.ResultsChan <- timings which can hang when ResultsChan is
nil or unbuffered; update Middleware to first check if m.ResultsChan != nil and
then perform a non-blocking send using a select with a default case so the
request path never stalls (i.e., attempt to send timings on m.ResultsChan in one
select case and use default to skip sending if it would block), leaving the rest
of the handler flow (next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()))
unchanged.

Comment thread router-tests/modules/custom-operation-timings/module.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@router-tests/modules/custom-operation-timings/module.go`:
- Around line 31-41: The ResultsChan is created unbuffered in
OperationTimingsModule.Module() so non-blocking sends can drop timings; change
the New function to initialize ResultsChan as a buffered channel (e.g.,
make(chan core.OperationTimings, 1)) inside the New closure that returns
&OperationTimingsModule to preserve the latest timing without blocking callers.

Comment thread router-tests/modules/custom-operation-timings/module.go
@SkArchon
Copy link
Copy Markdown
Contributor

SkArchon commented Feb 9, 2026

Hi, thanks for your contribution. Could you also add a PR description with your use case. We usually have descriptions on the PRs in addition to what is generated by coderabbit.

@AlenaSviridenko
Copy link
Copy Markdown
Contributor Author

@SkArchon Sorry I should have added it right away! Updated the description, please let me know what do you think?

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.05%. Comparing base (8546ee1) to head (f5bee3a).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2491      +/-   ##
==========================================
- Coverage   62.45%   56.05%   -6.41%     
==========================================
  Files         231      215      -16     
  Lines       24123    23548     -575     
==========================================
- Hits        15066    13199    -1867     
- Misses       7809     9107    +1298     
+ Partials     1248     1242       -6     
Files with missing lines Coverage Δ
router/core/context.go 73.49% <100.00%> (+0.18%) ⬆️

... and 103 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@StarpTech
Copy link
Copy Markdown
Contributor

StarpTech commented Feb 11, 2026

HI @AlenaSviridenko, please also create PR to document that functionality https://github.com/wundergraph/cosmo-docs/blob/main/docs/router/custom-modules.mdx Thank you!

@StarpTech StarpTech merged commit 010e8e2 into wundergraph:main Feb 11, 2026
30 checks passed
maxbol pushed a commit to maxbol/cosmo that referenced this pull request Feb 12, 2026
Co-authored-by: Alessandro Pagnin <ale@wundergraph.com>
@coderabbitai coderabbitai Bot mentioned this pull request Mar 3, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants